commit c4888cef7e32ab0438960020070050592d4bbaeb
parent baa3cf6e1213dd783eee4fe314551ebfd18d5929
Author: Alex Franchuk <afranchuk@mozilla.com>
Date: Mon, 15 Dec 2025 18:57:37 +0000
Bug 1989439 p1 - Create the crashping crate. r=gsvelto
This crate handles converting crash annotations to Glean metrics and
sending the crash ping.
Glean metrics are generated from `CrashAnnotations.yaml`.
Differential Revision: https://phabricator.services.mozilla.com/D264969
Diffstat:
16 files changed, 2294 insertions(+), 0 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1203,6 +1203,20 @@ dependencies = [
]
[[package]]
+name = "crashping"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "env_logger",
+ "glean",
+ "log",
+ "mozbuild",
+ "mozilla-central-workspace-hack",
+ "serde_json",
+ "time",
+]
+
+[[package]]
name = "crashreporter"
version = "1.0.0"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
@@ -24,6 +24,7 @@ members = [
"toolkit/crashreporter/client/app",
"toolkit/crashreporter/crash_helper_server",
"toolkit/crashreporter/crash_helper_client",
+ "toolkit/crashreporter/crashping",
"toolkit/crashreporter/minidump-analyzer/android/export",
"toolkit/crashreporter/mozwer-rust",
"toolkit/library/gtest/rust",
diff --git a/build/workspace-hack/Cargo.toml b/build/workspace-hack/Cargo.toml
@@ -268,6 +268,7 @@ optional = true
[features]
crash_helper_server = ["dep:cc", "dep:num-traits", "dep:log", "dep:once_cell", "dep:uuid", "dep:windows-sys"]
crashreporter = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:hyper", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:log", "dep:memchr", "dep:mio", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:toml", "dep:tracing", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"]
+crashping = ["dep:serde_json", "dep:time"]
geckodriver = ["dep:allocator-api2", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:clap", "dep:crossbeam-utils", "dep:flate2", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hyper", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:log", "dep:memchr", "dep:mio", "dep:num-integer", "dep:num-traits", "dep:once_cell", "dep:percent-encoding", "dep:regex", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:tracing", "dep:unicode-bidi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"]
gkrust = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:core-foundation-sys", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:log", "dep:memchr", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scopeguard", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:toml", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec"]
gkrust-gtest = ["gkrust"]
diff --git a/python/mach/mach/command_util.py b/python/mach/mach/command_util.py
@@ -87,6 +87,9 @@ MACH_COMMANDS = {
),
"configure": MachCommandReference("python/mozbuild/mozbuild/build_commands.py"),
"cppunittest": MachCommandReference("testing/mach_commands.py"),
+ "crash-ping-metrics": MachCommandReference(
+ "toolkit/crashreporter/crashping/glean_metrics.py"
+ ),
"crashtest": MachCommandReference("layout/tools/reftest/mach_commands.py"),
"data-review": MachCommandReference(
"toolkit/components/glean/build_scripts/mach_commands.py"
diff --git a/toolkit/crashreporter/crashping/Cargo.toml b/toolkit/crashreporter/crashping/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "crashping"
+version = "0.1.0"
+edition = "2021"
+rust-version.workspace = true
+
+[dependencies]
+anyhow = "1"
+glean = { workspace = true }
+log = "0.4"
+mozilla-central-workspace-hack = { version = "0.1", features = ["crashping"], optional = true }
+serde_json = "1"
+time = { version = "0.3" }
+
+[dev-dependencies]
+env_logger = { version = "0.10", default-features = false }
+
+[build-dependencies]
+mozbuild = "0.1"
diff --git a/toolkit/crashreporter/crashping/build.rs b/toolkit/crashreporter/crashping/build.rs
@@ -0,0 +1,34 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use std::path::Path;
+
+fn main() {
+ set_generated_files();
+}
+
+/// Set the GLEAN_METRICS_FILE and CONVERSIONS_FILE environment variables to the locations of the
+/// generated files.
+///
+/// We do this here to avoid a hardcoded path in the source (just in case this crate's src dir
+/// moves, not that it's likely).
+fn set_generated_files() {
+ let full_path = Path::new(env!("CARGO_MANIFEST_DIR"));
+ let relative_path = full_path
+ .strip_prefix(mozbuild::TOPSRCDIR)
+ .expect("CARGO_MANIFEST_DIR not a child of TOPSRCDIR");
+
+ for (file, env) in [
+ // Generated by glean_rust.py
+ ("glean_metrics.rs", "GLEAN_METRICS_FILE"),
+ // Generated by conversions.py
+ ("conversions.rs", "CONVERSIONS_FILE"),
+ ] {
+ let objpath = mozbuild::TOPOBJDIR.join(relative_path).join(file);
+ // We don't really need anything like `rerun-if-env-changed=CARGO_MANIFEST_DIR` because that
+ // will inevitably mean the entire crate has moved or the srcdir has moved, which would result
+ // in a rebuild anyway.
+ println!("cargo:rustc-env={env}={}", objpath.display());
+ }
+}
diff --git a/toolkit/crashreporter/crashping/conversions.py b/toolkit/crashreporter/crashping/conversions.py
@@ -0,0 +1,62 @@
+# 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/.
+
+import yaml
+
+
+def main(output, annotations_path):
+ with open(annotations_path) as annotations_file:
+ annotations = yaml.safe_load(annotations_file)
+
+ names = []
+
+ for name, data in annotations:
+ glean = data.get("glean")
+ if glean is None:
+ continue
+
+ annotation_type = data["type"]
+ glean_type = glean["type"]
+
+ namespace, _, metric = glean["metric"].rpartition(".")
+ namespace = namespace.replace(".", "_")
+
+ # Treat all numbers the same way
+ if annotation_type in ["usize", "u64", "u32"]:
+ annotation_type = "u64"
+
+ conversion = None
+ unique_conversion = f"convert_to_{namespace}_{metric}"
+ custom_convert = glean.get("custom_convert")
+ if custom_convert:
+ if custom_convert is True:
+ conversion = unique_conversion
+ else:
+ conversion = f"convert_{custom_convert}"
+
+ if conversion is None:
+ # Only use a standard conversion when there is no ambiguity
+ is_standard = (annotation_type, glean_type) in [
+ ("boolean", "boolean"),
+ ("string", "string"),
+ ("u64", "quantity"),
+ ("string", "string_list"),
+ ]
+ if is_standard:
+ conversion = f"convert_{annotation_type}_to_{glean_type}"
+ else:
+ conversion = unique_conversion
+
+ args = ""
+ if glean_type == "string_list":
+ args += f", \"{glean['delimiter']}\""
+
+ output.write(
+ f'#[allow(non_upper_case_globals)] pub const {name}: Annotation = convert!({namespace}::{metric} = {conversion}("{name}"{args}));\n'
+ )
+ names.append(name)
+
+ names = ",".join(names)
+
+ output.write(f"pub const ANNOTATIONS: &[Annotation] = &[{names}];\n")
diff --git a/toolkit/crashreporter/crashping/generated_metrics.yaml b/toolkit/crashreporter/crashping/generated_metrics.yaml
@@ -0,0 +1,1054 @@
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+# THIS FILE IS AUTO-GENERATED BY glean_metrics.py. DO NOT EDIT!
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+- 'Toolkit :: Crash Reporting'
+crash:
+ app_build:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Product application's build ID.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ app_channel:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Application release channel (e.g. default, beta, ...)
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ app_display_version:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Product version.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ async_shutdown_timeout:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: This annotation is present if a shutdown blocker was not released
+ in time and the browser was crashed instead of waiting for shutdown to finish.
+ The condition that caused the hang is contained in the annotation.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ structure:
+ properties:
+ broken_add_blockers:
+ items:
+ type: string
+ type: array
+ conditions:
+ type: string
+ phase:
+ type: string
+ type: object
+ type: object
+ background_task_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: If the app was invoked in background task mode via `--backgroundtask
+ <task name>`, the string "task name".
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ build_id:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Application build ID, the format is YYYYMMDDHHMMSS.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ crash_type:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: The type of crash that occurred (Android-only).
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ dom_fission_enabled:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set to 1 when DOM fission is enabled, and subframes are potentially
+ loaded in a separate process.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ event_loop_nesting_level:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Present only if higher than 0, indicates that we're running in a
+ nested event loop and indicates the nesting level.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: levels
+ font_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set before attempting to load a font to help diagnose crashes during
+ loading.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ gpu_process_launch:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Number of times the GPU process was launched.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: events
+ hang:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set if the crash was the result of a hang, with a value which describes
+ the type of hang (e.g. "ui" or "shutdown").
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ ipc_channel_error:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set before a content process crashes because of an IPC channel error.
+ Holds a description of the error.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ is_garbage_collecting:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: If true then the JavaScript garbage collector was running when the
+ crash occurred.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ java_exception:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: JSON structured Java stack trace, only present on Firefox for Android
+ if we encounter an uncaught Java exception.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ structure:
+ properties:
+ throwables:
+ items:
+ properties:
+ message:
+ type: string
+ stack:
+ items:
+ properties:
+ class_name:
+ type: string
+ file:
+ type: string
+ is_native:
+ type: boolean
+ line:
+ type: number
+ method_name:
+ type: string
+ type: object
+ type: array
+ type_name:
+ type: string
+ type: object
+ type: array
+ type: object
+ type: object
+ version: 1
+ last_interaction_duration:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: How long the user had been inactive in seconds if the user was inactive
+ at crash. The value is not set if the user state was active.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ time_unit: second
+ type: timespan
+ linux_memory_psi:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: 'Memory PSI (Pressure Stall Information) values from /proc/pressure/memory
+ as comma-separated list: some_avg10,some_avg60,some_avg300,some_total,full_avg10,full_avg60,full_avg300,full_total'
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ main_thread_runnable_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Name of the currently executing nsIRunnable on the main thread.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ minidump_sha_256_hash:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: The sha256 hash of the minidump file, if available.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ moz_crash_reason:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Plaintext description of why Firefox crashed, this is usually set
+ by assertions and the like.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ process_type:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Type of the process that crashed, the possible values are defined
+ in GeckoProcessTypes.h.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ product_id:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Application UUID (e.g. ec8030f7-c20a-464f-9b0e-13a3a9e97384).
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ product_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Application name (e.g. Firefox).
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ profiler_child_shutdown_phase:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: When a child process shuts down, this describes if the profiler is
+ running, and the point the profiler shutdown sequence has reached.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ quota_manager_shutdown_timeout:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: 'This annotation is present if the quota manager shutdown (resp.
+ the shutdown of the quota manager clients) was not finished in time and the
+ browser was crashed instead of waiting for the shutdown to finish. The status
+ of objects which were blocking completion of the shutdown when reaching the
+ timeout is contained in the annotation.
+
+ In the case of IndexedDB, objects are divided into three groups: FactoryOperations,
+ LiveDatabases and DatabaseMaintenances.
+
+ In the case of LocalStorage, objects are divided into three groups: PrepareDatastoreOperations,
+ Datastores and LiveDatabases.
+
+ In the case of Cache API, objects are in one group only: Managers.
+
+ Each group is reported separately and contains the number of objects in the
+ group and the status of individual objects in the group (duplicate entries are
+ removed): "GroupName: N (objectStatus1, objectStatus2, ...)" where N is the
+ number of objects in the group.
+
+ The status of individual objects is constructed by taking selected object properties.
+ Properties which contain origin strings are anonymized.
+
+ In addition, intermediate steps are recorded for change events after shutdown
+ started. These include the time difference and the type of object.'
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ structure:
+ items:
+ type: string
+ type: array
+ type: object
+ remote_type:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Type of the content process, can be set to "web", "file" or "extension".
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ shutdown_progress:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Shutdown step at which the browser crashed, can be set to "quit-application",
+ "profile-change-teardown", "profile-before-change", "xpcom-will-shutdown" or
+ "xpcom-shutdown".
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ shutdown_reason:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: One out of "Unknown", "AppClose", "AppRestart", "OSForceClose", "OSSessionEnd"
+ or "OSShutdown".
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ stack_traces:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Stack traces extracted from the crash minidump, if available. These
+ are sent in pings, however they are extracted and transformed into a different
+ format. The field in the extra file is meant only for client use.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ structure:
+ properties:
+ crash_address:
+ type: string
+ crash_thread:
+ type: number
+ crash_type:
+ type: string
+ error:
+ type: string
+ main_module:
+ type: number
+ modules:
+ items:
+ properties:
+ base_address:
+ type: string
+ code_id:
+ type: string
+ debug_file:
+ type: string
+ debug_id:
+ type: string
+ end_address:
+ type: string
+ filename:
+ type: string
+ version:
+ type: string
+ type: object
+ type: array
+ threads:
+ items:
+ properties:
+ frames:
+ items:
+ properties:
+ ip:
+ type: string
+ module_index:
+ type: number
+ trust:
+ type: string
+ type: object
+ type: array
+ type: object
+ type: array
+ type: object
+ type: object
+ startup:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: If set to 1 then this crash occurred during startup.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ time:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Crash time in seconds since the Epoch.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ time_unit: second
+ type: datetime
+ time_since_last_crash:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Time in seconds since the last crash occurred.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ time_unit: second
+ type: timespan
+ utility_actors_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Comma-separated list of IPC actors name running on this Utility process
+ instance
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string_list
+crash.windows:
+ error_reporting:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set to 1 if this crash was intercepted via the Windows Error Reporting
+ runtime exception module.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ file_dialog_error_code:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: The HRESULT returned from a Win32 system call leading to termination
+ of the file-dialog utility process. MozCrashReason is expected to provide context
+ for the value.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+dll_blocklist:
+ init_failed:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set to 1 if the DLL blocklist could not be initialized.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ list:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Semicolon-separated list of blocked DLLS, Windows-only
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string_list
+ user32_loaded_before:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Set to 1 if user32.dll was loaded before we could install the DLL
+ blocklist.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+environment:
+ headless_mode:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: True if the app was invoked in headless mode via `--headless ...`
+ or `--backgroundtask ...`, false otherwise.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: boolean
+ nimbus_enrollments:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: A comma-separated string that specifies the active Nimbus experiments
+ and rollouts, as well as their branches.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string_list
+ uptime:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Uptime in seconds. This annotation uses a string instead of an integer
+ because it has a fractional component.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ time_unit: millisecond
+ type: timespan
+memory:
+ available_commit:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: "Available commit-space in bytes. - Under Windows, computed from\
+ \ the PERFORMANCE_INFORMATION structure by substracting\n the CommitTotal field\
+ \ from the CommitLimit field.\n- Under Linux, computed from /proc/meminfo's\
+ \ CommitLimit - Committed_AS. Note that\n the kernel is not guaranteed to enforce\
+ \ that CommittedLimit >= Committed_AS. If\n Committed_AS > CommittedLimit,\
+ \ this value is set to 0.\n- Not available on other platforms."
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ available_physical:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Amount of free physical memory in bytes. - Under Windows, populated
+ with the contents of the MEMORYSTATUSEX's structure ullAvailPhys field. - Under
+ macOS, populated with vm_statistics64_data_t::free_count. - Under Linux, populated
+ with /proc/meminfo's MemFree. - Not available on other platforms.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ available_swap:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: "Amount of free swap space in bytes. - Under macOS, populated with\
+ \ the contents of\n sysctl \"vm.swapusage\" :: xsu_avail.\n- Under Linux, populated\
+ \ with /proc/meminfo's SwapFree. - Not available on other platforms."
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ available_virtual:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Amount of free virtual memory in bytes - Under Windows, populated
+ with the contents of the MEMORYSTATUSEX's structure ullAvailVirtual field. -
+ Under Linux, populated with /proc/meminfo's MemAvailable. - Not available on
+ other platforms. - For macOS, see AvailableSwapMemory, AvailablePhysicalMemory
+ and PurgeablePhysicalMemory.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ js_large_allocation_failure:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: A large allocation couldn't be satisfied, check the JSOutOfMemory
+ description for the possible values of this annotation.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ js_out_of_memory:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: A small allocation couldn't be satisfied, the annotation may contain
+ the "Reporting", "Reported" or "Recovered" value. The first one means that we
+ crashed while responding to the OOM condition (possibly while running a memory-pressure
+ observers), the second that we crashed after having tried to free some memory,
+ and the last that the GC had managed to free enough memory to satisfy the allocation.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
+ low_physical:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Number of times the available memory tracker has detected that free
+ physical memory is running low. This is a Windows-specific annotation.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: events
+ oom_allocation_size:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Size of the allocation that caused an out-of-memory condition.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ purgeable_physical:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: macOS only. Amount of physical memory currently allocated but which
+ may be deallocated by the system in case of memory pressure. Populated from
+ vm_statistics64_data_t::purgeable_count * vm_page_size.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ system_use_percentage:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Windows-only, percentage of physical memory in use. This annotation
+ is populated with the contents of the MEMORYSTATUSEX's structure dwMemoryLoad
+ field.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: percent
+ texture:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Amount of memory in bytes consumed by textures.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ total_page_file:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: "Maximum amount of memory that can be committed without extending\
+ \ the swap/page file. - Under Windows, populated with the contents of the PERFORMANCE_INFORMATION's\n\
+ \ structure CommitLimit field.\n- Under Linux, populated with /proc/meminfo\
+ \ MemTotal + SwapTotal. The swap file\n typically cannot be extended, so that's\
+ \ a hard limit.\n- Not available on other systems."
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ total_physical:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: Amount of physical memory in bytes. - Under Windows, populated with
+ the contents of the MEMORYSTATUSEX's structure ullTotalPhys field. - Under macOS,
+ populated with sysctl "hw.memsize". - Under Linux, populated with /proc/meminfo's
+ "MemTotal". - Not available on other systems.
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+ total_virtual:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: "Size of the virtual address space. - Under Windows, populated with\
+ \ the contents of the MEMORYSTATUSEX's structure\n ullTotalVirtual field.\n\
+ - Not available on other platforms."
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: quantity
+ unit: bytes
+windows:
+ package_family_name:
+ bugs:
+ - https://bugzilla.mozilla.org/1950749
+ data_reviews:
+ - https://bugzilla.mozilla.org/1950749
+ data_sensitivity:
+ - technical
+ description: 'If running in a Windows package context, the package family name,
+ per https://docs.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentpackagefamilyname.
+
+ The package family name is only included when it is likely to have been produced
+ by Mozilla: it starts "Mozilla." or "MozillaCorporation.".'
+ expires: never
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ send_in_pings:
+ - crash
+ type: string
diff --git a/toolkit/crashreporter/crashping/glean_metrics.py b/toolkit/crashreporter/crashping/glean_metrics.py
@@ -0,0 +1,115 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+import sys
+from os import path
+
+import yaml
+
+header = """
+# 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 http://mozilla.org/MPL/2.0/.
+
+# THIS FILE IS AUTO-GENERATED BY glean_metrics.py. DO NOT EDIT!
+
+"""
+source_path = path.join(path.dirname(__file__), "generated_metrics.yaml")
+
+
+def make_bugzilla_url(bug):
+ return f"https://bugzilla.mozilla.org/{bug}"
+
+
+def remove_non_glean_fields(glean):
+ """Remove all known fields in the glean descriptor which aren't directly
+ forwarded to the glean metrics definition file.
+ """
+ if glean["type"] == "string_list":
+ glean.pop("delimiter")
+ glean.pop("custom_convert", None)
+
+
+def generate_string(annotations):
+ output = {
+ "$schema": "moz://mozilla.org/schemas/glean/metrics/2-0-0",
+ "$tags": ["Toolkit :: Crash Reporting"],
+ }
+
+ for name, definition in annotations:
+ glean = definition.get("glean")
+ if glean is None:
+ continue
+
+ metric_category, _, metric_name = glean.pop("metric").rpartition(".")
+
+ remove_non_glean_fields(glean)
+
+ bug_urls = [
+ make_bugzilla_url(bug) for bug in definition.get("bugs", ["1950749"])
+ ]
+
+ glean.update(
+ {
+ "description": definition["description"].strip(),
+ "notification_emails": [
+ "crash-reporting-wg@mozilla.org",
+ "stability@mozilla.org",
+ ],
+ "bugs": bug_urls,
+ "data_reviews": bug_urls.copy(), # Copy so the produced yaml doesn't use anchors
+ "data_sensitivity": ["technical"],
+ "expires": "never",
+ "send_in_pings": ["crash"],
+ }
+ )
+
+ output.setdefault(metric_category, {})[metric_name] = glean
+
+ return header + yaml.safe_dump(output, explicit_start=True)
+
+
+def import_path(mod, path):
+ import importlib.util
+
+ spec = importlib.util.spec_from_file_location(mod, path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+def main(output, annotations_path):
+ with open(annotations_path) as annotations_file:
+ annotations = yaml.safe_load(annotations_file)
+ out = generate_string(annotations)
+
+ # Check whether the source path contents match what we would generate.
+ source_contents = ""
+ if path.exists(source_path):
+ with open(source_path) as file:
+ source_contents = file.read()
+ if source_contents != out:
+ sys.exit(f"{source_path} contents outdated; run ./mach crash-ping-metrics")
+
+ output.write(out)
+
+ return {source_path}
+
+
+if __name__.startswith("mach.commands"):
+ from mach.decorators import Command
+
+ @Command(
+ "crash-ping-metrics",
+ category="misc",
+ description="Generate the crash ping annotations Glean metrics files.",
+ )
+ def crash_ping_metrics(command_context):
+ load = import_path(
+ "annotations.load",
+ path.join(path.dirname(__file__), "..", "annotations", "load.py"),
+ )
+ annotations = load.read_annotations()
+ with open(source_path, "w") as file:
+ file.write(generate_string(annotations))
diff --git a/toolkit/crashreporter/crashping/glean_rust.py b/toolkit/crashreporter/crashping/glean_rust.py
@@ -0,0 +1,34 @@
+# 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/.
+
+import shutil
+import sys
+from contextlib import redirect_stderr, redirect_stdout
+from io import StringIO
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+
+def main(output, *paths):
+ # There's no way to just get the output as a string nor to write to our
+ # `output`, so we have to make a temporary directory for glean_parser to
+ # write to (which is ironic as glean_parser makes a temporary directory
+ # itself).
+ with TemporaryDirectory() as outdir:
+ outdir_path = Path(outdir)
+ # Capture translate output to only display on error
+ translate_output = StringIO()
+ with redirect_stdout(translate_output), redirect_stderr(translate_output):
+ # This is a bit tricky: sys.stderr is bound as a default argument
+ # in some functions of glean_parser, so we must redirect stderr
+ # _before_ importing the module.
+ from glean_parser.translate import translate
+
+ result = translate([Path(p) for p in paths], "rust", outdir_path)
+ if result != 0:
+ print(translate_output.getvalue())
+ sys.exit(result)
+ glean_metrics_file = outdir_path / "glean_metrics.rs"
+ with glean_metrics_file.open() as glean_metrics:
+ shutil.copyfileobj(glean_metrics, output)
diff --git a/toolkit/crashreporter/crashping/moz.build b/toolkit/crashreporter/crashping/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+# Generate the metrics file and ensure the srcdir metrics file is up-to-date.
+# The srcdir metrics file is necessary for the Glean probe-scraper to read and
+# create database tables.
+GeneratedFile(
+ "generated_metrics.yaml",
+ script="glean_metrics.py",
+ inputs=["!../annotations/validated.yaml"],
+)
+
+# Generate the Rust language bindings for the crash ping metrics.
+GeneratedFile(
+ "glean_metrics.rs",
+ script="glean_rust.py",
+ inputs=[
+ "/toolkit/components/glean/tags.yaml",
+ "pings.yaml",
+ "!generated_metrics.yaml",
+ ],
+)
+
+# Generate the conversion code for each annotation to Glean metrics.
+GeneratedFile(
+ "conversions.rs",
+ script="conversions.py",
+ inputs=["!../annotations/validated.yaml"],
+)
diff --git a/toolkit/crashreporter/crashping/pings.yaml b/toolkit/crashreporter/crashping/pings.yaml
@@ -0,0 +1,28 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+crash:
+ description: >
+ A ping to report crash information. This information is sent as soon as
+ possible after a crash occurs (whether the crash is a background/content
+ process or the main process). It is expected to be used for crash report
+ analysis and to reduce blind spots in crash reporting.
+ include_client_id: true
+ send_if_empty: false
+ notification_emails:
+ - crash-reporting-wg@mozilla.org
+ - stability@mozilla.org
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1790569
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1790569#c12
+ reasons:
+ crash: >
+ A process crashed and a ping was immediately sent.
+ event_found: >
+ A process crashed and produced a crash event, which was later found and
+ sent in a ping.
diff --git a/toolkit/crashreporter/crashping/src/annotations.rs b/toolkit/crashreporter/crashping/src/annotations.rs
@@ -0,0 +1,355 @@
+/* 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 super::glean_metrics;
+use anyhow::Context;
+use glean::private::{
+ BooleanMetric, DatetimeMetric, ObjectMetric, QuantityMetric, StringListMetric, StringMetric,
+ TimespanMetric,
+};
+use glean::TestGetValue;
+
+pub struct Annotation {
+ pub key: &'static str,
+ pub glean_key: &'static str,
+ #[cfg(test)]
+ pub convert_fn: &'static str,
+ pub set_glean_metric: fn(&serde_json::Value) -> anyhow::Result<()>,
+ pub test_get_glean_value: fn() -> Option<serde_json::Value>,
+}
+
+macro_rules! convert {
+ ( $category:ident :: $metric:ident = $func:ident ($key:literal $(, $($args:expr),*)? ) ) => {
+ Annotation {
+ key: $key,
+ glean_key: concat!(stringify!($category), ".", stringify!($metric)),
+ #[cfg(test)]
+ convert_fn: stringify!($func),
+ set_glean_metric: |value: &serde_json::Value| -> anyhow::Result<()> {
+ SourceValue(value).try_into().map_err(|e| anyhow::Error::from(e))
+ .and_then(|v| $func(&glean_metrics::$category::$metric, v $(, $($args),*)? ))
+ .context(concat!("while trying to set ", stringify!($category), "::", stringify!($metric), " from annotation ", $key))
+ },
+ test_get_glean_value: || -> Option<serde_json::Value> {
+ glean_metrics::$category::$metric.test_get_value(Some("crash".into()))
+ .map(|v| TestGetValueToJson(v).into())
+ }
+ }
+ };
+}
+
+// Env variable set to the file generated by conversions.py (by build.rs).
+include!(env!("CONVERSIONS_FILE"));
+
+struct SourceValue<'a>(&'a serde_json::Value);
+
+impl<'a> TryFrom<SourceValue<'a>> for &'a str {
+ type Error = anyhow::Error;
+
+ fn try_from(value: SourceValue<'a>) -> Result<Self, Self::Error> {
+ value.0.as_str().context("expected a string")
+ }
+}
+
+impl<'a> TryFrom<SourceValue<'a>> for &'a serde_json::Value {
+ type Error = std::convert::Infallible;
+
+ fn try_from(value: SourceValue<'a>) -> Result<Self, Self::Error> {
+ Ok(value.0)
+ }
+}
+
+struct TestGetValueToJson<T>(T);
+
+macro_rules! TestGetValueToJson_passthrough {
+ ( $($T:ty),* ) => {
+ $(
+ impl From<TestGetValueToJson<$T>> for serde_json::Value {
+ fn from(value: TestGetValueToJson<$T>) -> Self {
+ value.0.into()
+ }
+ })*
+ }
+}
+
+TestGetValueToJson_passthrough![serde_json::Value, String, bool, i64, Vec<String>];
+
+impl From<TestGetValueToJson<glean::Datetime>> for serde_json::Value {
+ fn from(value: TestGetValueToJson<glean::Datetime>) -> Self {
+ let glean::Datetime {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ nanosecond,
+ offset_seconds,
+ } = value.0;
+
+ time::OffsetDateTime::new_in_offset(
+ time::Date::from_calendar_date(year, (month as u8).try_into().unwrap(), day as u8)
+ .unwrap(),
+ time::Time::from_hms_nano(hour as u8, minute as u8, second as u8, nanosecond).unwrap(),
+ time::UtcOffset::from_whole_seconds(offset_seconds).unwrap(),
+ )
+ .to_string()
+ .into()
+ }
+}
+
+fn convert_boolean_to_boolean(metric: &BooleanMetric, value: &str) -> anyhow::Result<()> {
+ metric.set(value == "1");
+ Ok(())
+}
+
+fn convert_string_to_string(metric: &StringMetric, value: &str) -> anyhow::Result<()> {
+ metric.set(value.to_owned());
+ Ok(())
+}
+
+fn convert_u64_to_quantity(metric: &QuantityMetric, value: &str) -> anyhow::Result<()> {
+ metric.set(value.parse().context("couldn't parse quantity")?);
+ Ok(())
+}
+
+fn convert_string_to_string_list(
+ metric: &StringListMetric,
+ value: &str,
+ delimiter: &str,
+) -> anyhow::Result<()> {
+ metric.set(
+ value
+ .split(delimiter)
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_owned())
+ .collect(),
+ );
+ Ok(())
+}
+
+fn convert_to_crash_time(metric: &DatetimeMetric, value: &str) -> anyhow::Result<()> {
+ let seconds_since_epoch: i64 = value
+ .parse()
+ .context("failed to parse crash time seconds")?;
+ metric.set(Some(glean_datetime(
+ time::OffsetDateTime::from_unix_timestamp(seconds_since_epoch)
+ .context("failed to convert crash time unix timestamp")?,
+ )));
+ Ok(())
+}
+
+fn convert_duration_seconds(metric: &TimespanMetric, value: &str) -> anyhow::Result<()> {
+ metric.set_raw(std::time::Duration::from_secs(
+ value
+ .parse()
+ .context("failed to parse seconds since last crash")?,
+ ));
+ Ok(())
+}
+
+fn convert_to_environment_uptime(metric: &TimespanMetric, value: &str) -> anyhow::Result<()> {
+ metric.set_raw(std::time::Duration::from_secs_f32(
+ value.parse().context("failed to parse uptime")?,
+ ));
+ Ok(())
+}
+
+fn convert_to_crash_windows_file_dialog_error_code(
+ metric: &StringMetric,
+ value: &str,
+) -> anyhow::Result<()> {
+ metric.set(value.to_owned()); // Just assume it is an integer as expected
+ Ok(())
+}
+
+fn convert_to_crash_async_shutdown_timeout(
+ metric: &ObjectMetric<glean_metrics::crash::AsyncShutdownTimeoutObject>,
+ value: &str,
+) -> anyhow::Result<()> {
+ // The JSON value is stored as a string, so we need to deserialize it.
+ let mut value: serde_json::Map<String, serde_json::Value> = serde_json::from_str(value)?;
+ metric.set(glean_metrics::crash::AsyncShutdownTimeoutObject {
+ broken_add_blockers: {
+ if let Some(broken_add_blockers) = value.remove("brokenAddBlockers") {
+ let broken_add_blockers = broken_add_blockers
+ .as_array()
+ .context("expected array for brokenAddBlockers")?;
+ let mut ret = Vec::with_capacity(broken_add_blockers.len());
+ for entry in broken_add_blockers {
+ ret.push(
+ entry
+ .as_str()
+ .context("expected brokenAddBlockers entry to be a string")?
+ .to_owned(),
+ );
+ }
+ ret
+ } else {
+ Default::default()
+ }
+ },
+ conditions: {
+ if let Some(conditions) = value.remove("conditions") {
+ Some(if let Some(s) = conditions.as_str() {
+ s.to_owned()
+ } else {
+ serde_json::to_string(&conditions).context("failed to serialize conditions")?
+ })
+ } else {
+ None
+ }
+ },
+ phase: {
+ if let Some(phase) = value.remove("phase") {
+ Some(
+ phase
+ .as_str()
+ .context("expected phase to be a string")?
+ .to_owned(),
+ )
+ } else {
+ None
+ }
+ },
+ });
+ Ok(())
+}
+
+fn convert_to_crash_quota_manager_shutdown_timeout(
+ metric: &ObjectMetric<glean_metrics::crash::QuotaManagerShutdownTimeoutObject>,
+ value: &str,
+) -> anyhow::Result<()> {
+ // The Glean metric is an array of the lines.
+ metric.set(value.lines().map(|s| s.to_owned()).collect::<Vec<_>>());
+ Ok(())
+}
+
+fn convert_to_crash_stack_traces(
+ metric: &ObjectMetric<glean_metrics::crash::StackTracesObject>,
+ value: &serde_json::Value,
+) -> anyhow::Result<()> {
+ let error = value["status"]
+ .as_str()
+ .and_then(|v| (v != "OK").then_some(v.to_owned()));
+ let crash_type = value["crash_info"]["type"].as_str().map(|s| s.to_owned());
+ let crash_address = value["crash_info"]["address"]
+ .as_str()
+ .map(|s| s.to_owned());
+ let crash_thread = value["crash_info"]["crashing_thread"].as_i64();
+ let main_module = value["main_module"].as_i64();
+ let modules = value["modules"]
+ .as_array()
+ .map(|modules| {
+ modules
+ .iter()
+ .map(|m| glean_metrics::crash::StackTracesObjectModulesItem {
+ base_address: m["base_addr"].as_str().map(|s| s.to_owned()),
+ end_address: m["end_addr"].as_str().map(|s| s.to_owned()),
+ code_id: m["code_id"].as_str().map(|s| s.to_owned()),
+ debug_file: m["debug_file"].as_str().map(|s| s.to_owned()),
+ debug_id: m["debug_id"].as_str().map(|s| s.to_owned()),
+ filename: m["filename"].as_str().map(|s| s.to_owned()),
+ version: m["version"].as_str().map(|s| s.to_owned()),
+ })
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default();
+ let threads = value["threads"]
+ .as_array()
+ .map(|threads| {
+ threads
+ .iter()
+ .map(|t| glean_metrics::crash::StackTracesObjectThreadsItem {
+ frames: t["frames"]
+ .as_array()
+ .map(|frames| {
+ frames
+ .iter()
+ .map(|f| {
+ glean_metrics::crash::StackTracesObjectThreadsItemFramesItem {
+ module_index: f["module_index"].as_i64(),
+ ip: f["ip"].as_str().map(|s| s.to_owned()),
+ trust: f["trust"].as_str().map(|s| s.to_owned()),
+ }
+ })
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default(),
+ })
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default();
+ metric.set(glean_metrics::crash::StackTracesObject {
+ error,
+ crash_type,
+ crash_address,
+ crash_thread,
+ main_module,
+ modules,
+ threads,
+ });
+ Ok(())
+}
+
+// The format of the JSON value is defined in getStacktraceAsJsonString
+// (mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/ext/Throwable.kt).
+fn convert_to_crash_java_exception(
+ metric: &ObjectMetric<glean_metrics::crash::JavaExceptionObject>,
+ value: &str,
+) -> anyhow::Result<()> {
+ // The JSON value is stored as a string, so we need to deserialize it.
+ let value: serde_json::Map<String, serde_json::Value> = serde_json::from_str(value)?;
+ let throwables = value["exception"]["values"]
+ .as_array()
+ .context("expected throwables array")?
+ .iter()
+ // The format stores throwables in reverse order (deepest cause first), but we define the
+ // glean format to be by top cause first.
+ .rev()
+ .map(|throwable| &throwable["stacktrace"])
+ .map(
+ |throwable| glean_metrics::crash::JavaExceptionObjectThrowablesItem {
+ message: throwable["value"].as_str().map(ToOwned::to_owned),
+ type_name: throwable["module"]
+ .as_str()
+ .zip(throwable["type"].as_str())
+ .map(|(m, t)| format!("{m}.{t}")),
+ stack: throwable["frames"]
+ .as_array()
+ .map(|frames| {
+ frames
+ .iter()
+ .map(|frame| {
+ glean_metrics::crash::JavaExceptionObjectThrowablesItemStackItem {
+ class_name: frame["module"].as_str().map(ToOwned::to_owned),
+ method_name: frame["function"].as_str().map(ToOwned::to_owned),
+ is_native: frame["in_app"].as_bool().map(|b| !b),
+ line: frame["lineno"].as_i64(),
+ file: frame["filename"].as_str().map(ToOwned::to_owned),
+ }
+ })
+ .collect()
+ })
+ .unwrap_or_default(),
+ },
+ )
+ .collect();
+
+ metric.set(glean_metrics::crash::JavaExceptionObject { throwables });
+ Ok(())
+}
+
+fn glean_datetime(datetime: time::OffsetDateTime) -> glean::Datetime {
+ glean::Datetime {
+ year: datetime.year(),
+ month: datetime.month() as _,
+ day: datetime.day() as _,
+ hour: datetime.hour() as _,
+ minute: datetime.minute() as _,
+ second: datetime.second() as _,
+ nanosecond: datetime.nanosecond(),
+ offset_seconds: datetime.offset().whole_seconds(),
+ }
+}
diff --git a/toolkit/crashreporter/crashping/src/lib.rs b/toolkit/crashreporter/crashping/src/lib.rs
@@ -0,0 +1,540 @@
+/* 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/. */
+
+//! The crashping crate allows populating and sending the crash ping, which contains all
+//! ping-scoped crash annotations.
+
+pub use glean::net;
+pub use 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;
+use annotations::ANNOTATIONS;
+
+const TELEMETRY_SERVER: &str = "https://incoming.telemetry.mozilla.org";
+
+/// 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.
+pub fn 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.
+pub struct InitGlean {
+ pub configuration: Configuration,
+ pub client_info_metrics: ClientInfoMetrics,
+ pub clear_uploader_for_tests: bool,
+}
+
+impl InitGlean {
+ /// The data_dir should be a dedicated directory for use by Glean.
+ pub fn 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,
+ }
+ }
+
+ pub fn initialize(self) {
+ self.init_with(glean::initialize)
+ }
+
+ pub fn test_reset_glean(self, clear_stores: bool) {
+ self.init_with(move |c, m| glean::test_reset_glean(c, m, clear_stores))
+ }
+
+ pub fn init_with<F: FnOnce(Configuration, ClientInfoMetrics)>(self, 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();
+ if self.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.
+pub fn 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(())
+}
+
+/// **Test-only API**
+///
+/// Register a callback that will be called before the next ping is sent.
+pub fn 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.
+pub fn test_get_metric_values() -> serde_json::Value {
+ let mut ret: serde_json::Map<String, serde_json::Value> = Default::default();
+ for annotation in ANNOTATIONS {
+ if let 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 {
+ if let Some(value) = annotations.get(annotation.key) {
+ (annotation.set_glean_metric)(value)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod test {
+ use super::{send, test_before_next_send, ANNOTATIONS};
+ use std::sync::{
+ atomic::{AtomicBool, Ordering::Relaxed},
+ Arc, Mutex,
+ };
+
+ static TEST_LOCK: Mutex<()> = Mutex::new(());
+
+ #[must_use]
+ fn test_init_glean() -> std::sync::MutexGuard<'static, ()> {
+ let guard = TEST_LOCK.lock().unwrap();
+ let glean_path = std::env::temp_dir().join("crashping_glean");
+ super::InitGlean::new(
+ glean_path,
+ "crashping.test",
+ super::ClientInfoMetrics::unknown(),
+ )
+ .test_reset_glean(true);
+ return guard;
+ }
+
+ /// 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)
+ };
+ if let Err(e) = res {
+ std::panic::resume_unwind(e);
+ }
+ }
+
+ #[derive(Clone)]
+ struct SoftAssert {
+ failed: Arc<AtomicBool>,
+ message: &'static str,
+ }
+
+ impl SoftAssert {
+ fn new(failed: bool, message: &'static str) -> Self {
+ SoftAssert {
+ failed: Arc::new(AtomicBool::new(failed)),
+ message,
+ }
+ }
+
+ fn assert<T: std::fmt::Display>(&self, value: bool, msg: T) {
+ if !value {
+ eprintln!("{}", msg);
+ self.failed.store(true, Relaxed);
+ }
+ }
+
+ fn clear(&self) {
+ self.failed.store(false, Relaxed);
+ }
+ }
+
+ impl Drop for SoftAssert {
+ fn drop(&mut self) {
+ assert!(!self.failed.load(Relaxed), "{}", self.message);
+ }
+ }
+
+ #[test]
+ fn all_annotations() {
+ glean_test(|| {
+ // Set test values here if the default values aren't sufficient nor applicable.
+ let mut annotations = serde_json::json!({
+ "AsyncShutdownTimeout": "{\"phase\":\"abcd\",\"conditions\":[{\"foo\":\"bar\"}],\"brokenAddBlockers\":[\"foo\"]}",
+ "BlockedDllList": "Foo.dll;bar.dll;rawr.dll",
+ "CrashTime": "1234",
+ "LastInteractionDuration": "100",
+ "NimbusEnrollments": "a,b,c,d,e",
+ "QuotaManagerShutdownTimeout": "line1\nline2\nline3",
+ "JavaException": r#"{
+ "exception": {
+ "values": [{
+ "stacktrace": {
+ "value": "something went wrong",
+ "module": "foo.bar",
+ "type": "FooBarType",
+ "frames": [{
+ "module": "org.mozilla",
+ "function": "FooBar",
+ "in_app": true,
+ "lineno": 42,
+ "file": "FooBar.java"
+ }]
+ }
+ }]
+ }
+ }"#,
+ "SecondsSinceLastCrash": "50000",
+ "StackTraces": {
+ "status": "OK",
+ // Add extraneous field to ensure it doesn't affect setting the metric
+ "foobar": "baz",
+ "crash_info": {
+ "type": "bad crash",
+ "address": "0xcafe",
+ "crashing_thread": 1
+ },
+ "main_module": 0,
+ "modules": [{
+ "base_addr": "0xcafe",
+ "end_addr": "0xf000",
+ "code_id": "CODEID",
+ "debug_file": "debug_file.so",
+ "debug_id": "DEBUGID",
+ "filename": "file.so",
+ "version": "1.0.0"
+ }],
+ "threads": [
+ {"frames": [
+ {
+ "ip": "0xf00",
+ "trust": "crash",
+ "module_index": 0
+ }
+ ]},
+ {"frames": [
+ {
+ "ip": "0x0",
+ "trust": "crash",
+ "module_index": 0
+ },
+ {
+ "ip": "0xbadf00d",
+ "trust": "cfi",
+ "module_index": 0
+ }
+ ]}
+ ]
+ },
+ "UptimeTS": "400.5",
+ "UtilityActorsName": "abc,def",
+ "WindowsFileDialogErrorCode": "40",
+ });
+
+ // 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,
+ };
+ if let 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");
+ });
+ }
+
+ fn test_annotation(
+ annotation: &'static crate::annotations::Annotation,
+ value: serde_json::Value,
+ expected: serde_json::Value,
+ ) {
+ glean_test(|| {
+ let annotations = serde_json::json!({annotation.key: value});
+
+ 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)();
+ if let 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");
+ });
+ }
+
+ macro_rules! test_annotations {
+ ( ) => {};
+ ( $test_name:ident ($annotation:ident) { $($value:tt => $expected:tt),+ } $($rest:tt)* ) => {
+ #[test]
+ fn $test_name() {
+ $(test_annotation(&crate::annotations::$annotation, serde_json::json!($value), serde_json::json!($expected));)*
+ }
+
+ test_annotations! { $($rest)* }
+ };
+ }
+
+ test_annotations! {
+ test_async_shutdown_timeout(AsyncShutdownTimeout) {
+ r#"{"phase":"AddonManager: Waiting to start provider shutdown.","conditions":[{"name":"AddonRepository Background Updater","state":"(none)","filename":"resource://gre/modules/addons/AddonRepository.sys.mjs","lineNumber":576,"stack":["resource://gre/modules/addons/AddonRepository.sys.mjs:backgroundUpdateCheck:576","resource://gre/modules/AddonManager.sys.mjs:backgroundUpdateCheck/buPromise<:1269"]}],"brokenAddBlockers":["JSON store: writing data for 'creditcards' - IOUtils: waiting for profileBeforeChange IO to complete finished","StorageSyncService: shutdown - profile-change-teardown finished"]}"#
+ => {
+ "phase": "AddonManager: Waiting to start provider shutdown.",
+ "conditions": serde_json::json!([
+ {
+ "name": "AddonRepository Background Updater",
+ "state": "(none)",
+ "filename": "resource://gre/modules/addons/AddonRepository.sys.mjs",
+ "lineNumber": 576,
+ "stack": [
+ "resource://gre/modules/addons/AddonRepository.sys.mjs:backgroundUpdateCheck:576",
+ "resource://gre/modules/AddonManager.sys.mjs:backgroundUpdateCheck/buPromise<:1269",
+ ]
+ }
+ ]).to_string(),
+ "broken_add_blockers": [
+ "JSON store: writing data for 'creditcards' - IOUtils: waiting for profileBeforeChange IO to complete finished",
+ "StorageSyncService: shutdown - profile-change-teardown finished"
+ ]
+ }
+ }
+
+ test_blocked_dll_list(BlockedDllList) {
+ "Foo.dll;bar.dll;rawr.dll" => ["Foo.dll", "bar.dll", "rawr.dll"]
+ }
+
+ test_java_exception(JavaException) {
+ r#"{
+ "exception": {
+ "values": [{
+ "stacktrace": {
+ "value": "something went wrong",
+ "module": "foo.bar",
+ "type": "FooBarType",
+ "frames": [{
+ "module": "org.mozilla",
+ "function": "FooBar",
+ "in_app": true,
+ "lineno": 42,
+ "filename": "FooBar.java"
+ }]
+ }
+ }]
+ }
+ }"#
+ => {
+ "throwables": [
+ {
+ "message": "something went wrong",
+ "type_name": "foo.bar.FooBarType",
+ "stack": [
+ {
+ "class_name": "org.mozilla",
+ "method_name": "FooBar",
+ "file": "FooBar.java",
+ "is_native": false,
+ "line": 42
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+ test_nimbus_enrollments(NimbusEnrollments) {
+ "foo:control,bar:treatment-a" => ["foo:control", "bar:treatment-a"]
+ }
+
+ test_quota_manager_shutdown_timeout(QuotaManagerShutdownTimeout) {
+ "foo\nbar\nbaz" => ["foo", "bar", "baz"]
+ }
+
+ test_stack_traces(StackTraces) {
+ {
+ "status": "OK",
+ "crash_info": {
+ "type": "main",
+ "address": "0xf001ba11",
+ "crashing_thread": 1
+ },
+ "main_module": 0,
+ "modules": [
+ {
+ "base_addr": "0x00000000",
+ "end_addr": "0x00004000",
+ "code_id": "8675309",
+ "debug_file": "",
+ "debug_id": "18675309",
+ "filename": "foo.exe",
+ "version": "1.0.0"
+ },
+ {
+ "base_addr": "0x00004000",
+ "end_addr": "0x00008000",
+ "code_id": "42",
+ "debug_file": "foo.pdb",
+ "debug_id": "43",
+ "filename": "foo.dll",
+ "version": "1.1.0"
+ }
+ ],
+ "threads": [
+ {
+ "frames": [
+ { "module_index": 0, "ip": "0x10", "trust": "context" },
+ { "module_index": 0, "ip": "0x20", "trust": "cfi" }
+ ]
+ },
+ {
+ "frames": [
+ { "module_index": 1, "ip": "0x4010", "trust": "context" },
+ { "module_index": 0, "ip": "0x30", "trust": "cfi" }
+ ]
+ }
+ ]
+ }
+ => {
+ "crash_type": "main",
+ "crash_address": "0xf001ba11",
+ "crash_thread": 1,
+ "main_module": 0,
+ "modules": [
+ {
+ "base_address": "0x00000000",
+ "end_address": "0x00004000",
+ "code_id": "8675309",
+ "debug_file": "",
+ "debug_id": "18675309",
+ "filename": "foo.exe",
+ "version": "1.0.0",
+ },
+ {
+ "base_address": "0x00004000",
+ "end_address": "0x00008000",
+ "code_id": "42",
+ "debug_file": "foo.pdb",
+ "debug_id": "43",
+ "filename": "foo.dll",
+ "version": "1.1.0",
+ },
+ ],
+ "threads": [
+ {
+ "frames": [
+ { "module_index": 0, "ip": "0x10", "trust": "context" },
+ { "module_index": 0, "ip": "0x20", "trust": "cfi" },
+ ],
+ },
+ {
+ "frames": [
+ { "module_index": 1, "ip": "0x4010", "trust": "context" },
+ { "module_index": 0, "ip": "0x30", "trust": "cfi" },
+ ],
+ },
+ ],
+ }
+ }
+
+ test_utility_actors_name(UtilityActorsName) {
+ "audio-decoder-generic,js-oracle" => ["audio-decoder-generic","js-oracle"]
+ }
+ }
+}
diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build
@@ -84,6 +84,7 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"crash_helper",
"crash_helper_client",
"crash_helper_server",
+ "crashping",
"mozannotation_client",
]
diff --git a/toolkit/library/rust/moz.build b/toolkit/library/rust/moz.build
@@ -21,6 +21,7 @@ for feature in gkrust_features:
# Target directory doesn't matter a lot here, since we can't share panic=abort
# compilation artifacts with gkrust.
RUST_TESTS = [
+ "crashping",
"crashreporter",
"firefox-on-glean",
"l10nregistry",